Disney Vacation Club Resort Maintenance Fees

Which resorts will have the lowest maintenance fees in the future? More info about Disney Vacation Club.

Refresh 11/30/2021

In [1]:
import pandas, numpy, seaborn, matplotlib, unicodedata, plotly.graph_objects
import logging, sys, os
from warnings import filterwarnings, resetwarnings
from fbprophet import Prophet
from fbprophet.plot import plot_plotly
import plotly.offline as py
py.init_notebook_mode()
pandas.set_option('display.float_format', lambda x: '%.3f' %x if not numpy.isnan(x) else '-')
pandas.options.display.max_colwidth = None
In [2]:
format_accounting = lambda x: '%.3f' %x if not numpy.isnan(x) else '-'
format_number = '{:.0f}'.format
format_float = '{:,.3f}'.format
format_dollar = '<span>$</span>{:,.3f}'.format
max_file_size = 5368709120
file_type = 'CSV'
field_delimiter = '\t'
timestamp_format = 'YYYY-MM-DD HH24:MI:SS.FF'
field_escape = '\\\\'
file_extension = 'txt'
file_compression = 'gzip'
parallel = 99
In [3]:
dvc_resale_market = pandas.read_csv('dvcresalemarket.txt',
                                                                delimiter=field_delimiter,
                                                                header=0,
                                                                keep_default_na=True,
                                                                na_filter=True,
                                                                infer_datetime_format=True,
                                                                memory_map=True,
                                                                encoding='windows-1252'
                                   )
maintenance_fees = pandas.read_csv('maintenance_fees.txt',
                                                                delimiter=field_delimiter,
                                                                header=0,
                                                                keep_default_na=True,
                                                                na_filter=True,
                                                                infer_datetime_format=True,
                                                                memory_map=True,
                                                                encoding='windows-1252'
#                                                                 encoding='iso-8859-1'
                                   )
years_left = pandas.read_csv('years_left.txt',
                                                                delimiter=field_delimiter,
                                                                header=0,
                                                                keep_default_na=True,
                                                                na_filter=True,
                                                                infer_datetime_format=True,
                                                                memory_map=True,
                                                                encoding='windows-1252'                                   )

Data Import

In [4]:
dvc_resale_market
Out[4]:
RESORT PTS. 19 20 21 USE YEAR $/PT. PRICE STATUS COST/PT TOTAL
0 Grand Floridian 160 0 0 160 Jun 151 24160 SALE PENDING 3.432 -
1 Grand Floridian 167 0 7 167 Oct 152 25384 SALE PENDING 3.451 -
2 Grand Floridian 100 0 3 200 Feb 156 15600 REDUCED 3.464 -
3 Grand Floridian 90 0 0 180 Feb 157 14130 REDUCED 3.489 -
4 Grand Floridian 150 0 197 150 Dec 160 24000 SALE PENDING 3.531 -
... ... ... ... ... ... ... ... ... ... ... ...
564 Hilton Head 200 0 276 200 Jun 82 16400 AVAILABLE 3.507 -
565 Hilton Head 240 220 240 240 Dec 85 20400 NEARLY DOUBLE POINTS 3.554 -
566 Hilton Head 200 0 0 1 Mar 75 15000 NEW 3.571 -
567 Hilton Head 200 0 0 0 Aug 76 15200 NEW 3.619 -
568 Hilton Head 50 0 0 100 Feb 88 4400 SALE PENDING 3.826 -

569 rows × 11 columns

In [5]:
maintenance_fees
Out[5]:
Resort Year Maintenance Fees
0 Animal Kingdom 2021 8.070
1 Animal Kingdom 2020 7.670
2 Animal Kingdom 2019 7.440
3 Animal Kingdom 2018 6.760
4 Animal Kingdom 2017 6.590
... ... ... ...
237 Vero Beach 2003 4.360
238 Vero Beach 2002 4.170
239 Vero Beach 2001 3.970
240 Vero Beach 2000 4.080
241 Vero Beach 1999 3.990

242 rows × 3 columns

In [6]:
years_left
Out[6]:
Resort Yrs Left
0 Aulani 42
1 Animal Kingdom 37
2 Beach Club 22
3 Bay Lake Tower 40
4 Boulder Ridge 22
5 Boardwalk 22
6 Copper Creek 48
7 Hilton Head 22
8 Old Key West 22
9 Polynesian 46
10 Riviera Resort 49
11 Saratoga Springs 34
12 Vero Beach 22
13 Grand Californian 40
14 Grand Floridian 44
In [7]:
maintenance_fees.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 242 entries, 0 to 241
Data columns (total 3 columns):
 #   Column            Non-Null Count  Dtype  
---  ------            --------------  -----  
 0   Resort            242 non-null    object 
 1   Year              242 non-null    int64  
 2   Maintenance Fees  242 non-null    float64
dtypes: float64(1), int64(1), object(1)
memory usage: 5.8+ KB
In [8]:
maintenance_fees.replace(u'\\xa0',u' ', regex=True, inplace=True)
In [9]:
resorts = sorted(list(maintenance_fees.Resort.unique()))
resorts
Out[9]:
['Animal Kingdom',
 'Aulani',
 'Bay Lake Tower',
 'Beach Club',
 'Boardwalk',
 'Boulder Ridge',
 'Copper Creek',
 'Grand Californian',
 'Grand Floridian',
 'Hilton Head',
 'Old Key West',
 'Polynesian',
 'Riviera Resort',
 'Saratoga Springs',
 'Vero Beach']

All Time Average Maintenance Fees

In [10]:
maintenance_fees.groupby(['Resort'], as_index = False)[['Maintenance Fees']].mean()\
    .sort_values("Maintenance Fees")\
    .reset_index(drop=True)\
    .style.format({'Maintenance Fees':format_dollar}).hide_index()
Out[10]:
Resort Maintenance Fees
Old Key West $4.468
Saratoga Springs $5.003
Bay Lake Tower $5.122
Grand Californian $5.196
Boardwalk $5.217
Hilton Head $5.370
Beach Club $5.436
Boulder Ridge $5.442
Grand Floridian $5.982
Animal Kingdom $6.032
Polynesian $6.436
Vero Beach $6.641
Aulani $6.980
Copper Creek $7.410
Riviera Resort $8.333

Average Maintenance Fees (Since 2015)

In [11]:
maintenance_fees[maintenance_fees.Year>=2015].groupby(['Resort'], as_index = False)[['Maintenance Fees']].mean()\
    .sort_values("Maintenance Fees")\
    .reset_index(drop=True)\
    .style.format({'Maintenance Fees':format_dollar}).hide_index()
Out[11]:
Resort Maintenance Fees
Bay Lake Tower $5.964
Grand Californian $5.981
Saratoga Springs $6.050
Grand Floridian $6.146
Polynesian $6.436
Beach Club $6.607
Boardwalk $6.803
Old Key West $6.916
Boulder Ridge $6.990
Animal Kingdom $7.036
Copper Creek $7.410
Aulani $7.486
Hilton Head $7.994
Riviera Resort $8.333
Vero Beach $9.089

Pivot Table

In [12]:
maintenance_fees["Year"] = maintenance_fees.Year.astype('category')
pandas.pivot_table(
    maintenance_fees
    ,index=["Year"]
    ,columns=["Resort"]
    ,values='Maintenance Fees'
    ,aggfunc=(numpy.sum)
).reset_index()\
.sort_index(ascending = False)\
.style.format(format_accounting).format({'Year':format_number}).background_gradient(cmap='BuPu')\
.hide_index()
Out[12]:
Year Animal Kingdom Aulani Bay Lake Tower Beach Club Boardwalk Boulder Ridge Copper Creek Grand Californian Grand Floridian Hilton Head Old Key West Polynesian Riviera Resort Saratoga Springs Vero Beach
2021 8.070 8.350 6.900 7.440 7.810 8.110 7.590 6.990 6.810 9.970 8.360 7.050 8.380 7.110 11.230
2020 7.670 8.330 6.580 7.060 7.370 7.780 7.450 6.600 6.560 9.100 7.840 6.790 8.310 6.770 10.130
2019 7.440 7.860 6.400 6.940 7.170 7.320 7.420 6.270 6.390 8.560 7.230 6.760 8.310 6.400 9.480
2018 6.760 7.530 5.920 6.440 6.550 6.930 7.260 5.880 6.130 7.720 6.720 6.200 - 5.860 8.530
2017 6.590 7.030 5.620 6.270 6.470 6.540 7.330 5.610 5.900 7.270 6.410 6.140 - 5.600 8.110
2016 6.420 6.790 5.280 6.130 6.180 6.220 - 5.370 5.710 6.820 6.010 6.090 - 5.440 8.080
2015 6.300 6.510 5.050 5.970 6.070 6.030 - 5.150 5.520 6.520 5.840 6.020 - 5.170 8.060
2014 5.970 6.440 4.780 5.790 6.010 5.930 - 4.940 5.410 6.280 5.540 - - 4.910 7.750
2013 5.670 6.250 4.500 5.650 5.840 5.790 - 4.580 5.410 6.020 5.340 - - 4.810 7.410
2012 5.440 5.960 4.220 5.500 5.620 5.610 - 4.330 - 5.930 5.200 - - 4.730 7.120
2011 5.010 5.730 3.890 5.280 5.460 5.340 - 4.070 - 5.680 4.980 - - 4.510 6.780
2010 4.950 - 3.780 5.150 5.360 5.200 - 3.940 - 5.570 4.870 - - 4.460 6.610
2009 4.860 - 3.670 5.000 5.210 5.040 - 3.820 - 5.360 4.730 - - 4.340 6.410
2008 4.710 - - 4.800 5.040 4.870 - - - 5.160 4.560 - - 4.210 6.040
2007 4.620 - - 4.630 4.850 4.730 - - - 4.980 4.400 - - 4.120 5.630
2006 - - - 4.480 4.690 4.610 - - - 4.340 4.240 - - 3.980 5.270
2005 - - - 4.270 4.410 4.350 - - - 4.040 3.860 - - 3.830 4.870
2004 - - - 4.180 4.250 4.220 - - - 3.860 3.680 - - 3.800 4.670
2003 - - - 3.970 4.110 4.050 - - - 3.700 3.490 - - - 4.360
2002 - - - 3.770 3.920 3.800 - - - 3.480 3.220 - - - 4.170
2001 - - - - 3.820 3.630 - - - 3.320 3.130 - - - 3.970
2000 - - - - 3.940 3.620 - - - 3.250 3.160 - - - 4.080
1999 - - - - 4.020 - - - - 3.180 3.160 - - - 3.990
1998 - - - - 3.940 - - - - 3.200 3.170 - - - -
1997 - - - - 3.840 - - - - 3.160 3.140 - - - -
1996 - - - - 3.700 - - - - 3.160 2.990 - - - -
1995 - - - - - - - - - - 2.840 - - - -
1994 - - - - - - - - - - 2.700 - - - -
1993 - - - - - - - - - - 2.630 - - - -
1992 - - - - - - - - - - 2.560 - - - -
1991 - - - - - - - - - - 2.510 - - - -
In [13]:
maintenance_fees["Year"] = maintenance_fees.Year.astype('int')

Polynesian Maintenance Fee History

In [14]:
maintenance_fees[maintenance_fees.Resort == 'Polynesian'].reset_index(drop=True).style.format(
    {'Maintenance Fees':format_float,'y':format_float}).hide_index()
Out[14]:
Resort Year Maintenance Fees
Polynesian 2021 7.050
Polynesian 2020 6.790
Polynesian 2019 6.760
Polynesian 2018 6.200
Polynesian 2017 6.140
Polynesian 2016 6.090
Polynesian 2015 6.020

fbprophet to Forecast Future Fees

In [15]:
maintenance_fees['ds'] = pandas.to_datetime(maintenance_fees.Year*10000+101, format='%Y%m%d')
maintenance_fees['y'] = maintenance_fees['Maintenance Fees']

fbprophet_columns = {'Maintenance Fees':'y'}

Interactive Charts Rendered Only in HTML

In [16]:
logging.getLogger().setLevel(50)
logging.disable(sys.maxsize)

logger = logging.getLogger()
logger.setLevel(logging.CRITICAL)
try:
    del forecast_frame
except NameError:
    False
    
for resort in resorts:
#     filterwarnings("ignore")
    m = Prophet()
    m.fit(maintenance_fees[['ds','y']][maintenance_fees.Resort == resort])
    
    future = m.make_future_dataframe(periods=22, freq = 'Y')

    forecast = m.predict(future)
    forecast['resort'] = resort
    
    try:
        forecast_frame
    except NameError:
        forecast_frame = forecast.copy(deep=False)
    
    forecast_frame = forecast_frame.append(forecast, ignore_index=True)
    
    fig1 = m.plot(forecast) # use for GitHub preview
    fig1.axes[0].set_title(resort)

#     fig2 = m.plot_components(forecast) # not relevant for this case

    fig = plot_plotly(m, forecast)  # This returns a plotly Figure - better for interactive
    fig.update_layout(title_text=resort)
    py.iplot(fig)
    
#     resetwarnings()
    del m

logging.disable(logging.NOTSET)    
In [17]:
forecast_frame
Out[17]:
ds trend yhat_lower yhat_upper trend_lower trend_upper additive_terms additive_terms_lower additive_terms_upper yearly yearly_lower yearly_upper multiplicative_terms multiplicative_terms_lower multiplicative_terms_upper yhat resort
0 2007-01-01 0.176 4.453 4.667 0.176 0.176 4.384 4.384 4.384 4.384 4.384 4.384 0.000 0.000 0.000 4.559 Animal Kingdom
1 2008-01-01 0.324 4.615 4.824 0.324 0.324 4.397 4.397 4.397 4.397 4.397 4.397 0.000 0.000 0.000 4.722 Animal Kingdom
2 2009-01-01 0.474 4.715 4.928 0.474 0.474 4.351 4.351 4.351 4.351 4.351 4.351 0.000 0.000 0.000 4.825 Animal Kingdom
3 2010-01-01 0.623 4.889 5.096 0.623 0.623 4.368 4.368 4.368 4.368 4.368 4.368 0.000 0.000 0.000 4.991 Animal Kingdom
4 2011-01-01 0.772 5.046 5.270 0.772 0.772 4.384 4.384 4.384 4.384 4.384 4.384 0.000 0.000 0.000 5.156 Animal Kingdom
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
604 2038-12-31 16.247 16.901 17.959 15.896 16.610 1.208 1.208 1.208 1.208 1.208 1.208 0.000 0.000 0.000 17.455 Vero Beach
605 2039-12-31 16.616 17.527 18.690 16.236 17.006 1.506 1.506 1.506 1.506 1.506 1.506 0.000 0.000 0.000 18.122 Vero Beach
606 2040-12-31 16.985 17.200 18.348 16.580 17.404 0.778 0.778 0.778 0.778 0.778 0.778 0.000 0.000 0.000 17.763 Vero Beach
607 2041-12-31 17.354 17.729 18.927 16.920 17.795 0.965 0.965 0.965 0.965 0.965 0.965 0.000 0.000 0.000 18.319 Vero Beach
608 2042-12-31 17.722 18.273 19.550 17.260 18.192 1.208 1.208 1.208 1.208 1.208 1.208 0.000 0.000 0.000 18.930 Vero Beach

609 rows × 17 columns

In [18]:
forecast_frame.to_csv(path_or_buf='fbprophet_predictions.csv')
In [19]:
f, axes = matplotlib.pyplot.subplots(1,1, figsize=(15,5), sharex = False, sharey = False)
seaborn.despine()
seaborn_whitegrid = {'axes.grid': False,'axes.spines.right': False, 'axes.spines.top': False,  'axes.edgecolor': '.15',}
with seaborn.axes_style(seaborn_whitegrid):
    seaborn.lineplot(x="ds", y="yhat", hue="resort", legend='full',
                  data=forecast_frame)
    matplotlib.pyplot.xlabel('Year')
    matplotlib.pyplot.ylabel('Maitenance Fees')

Average Maintenance Fees (After 2020)

In [20]:
forecast_frame[forecast_frame.ds>'2020-01-01'].rename(columns={'ds':'date','yhat':'avg predicted fees'})\
                                                        .groupby(['resort'], as_index = False)[['avg predicted fees']].mean()\
    .sort_values("avg predicted fees")\
    .reset_index(drop=True)\
    .style.format({'avg predicted fees':format_dollar}).hide_index()
Out[20]:
resort avg predicted fees
Copper Creek $8.084
Riviera Resort $8.352
Grand Floridian $8.814
Polynesian $9.527
Boardwalk $10.166
Bay Lake Tower $10.331
Beach Club $10.525
Grand Californian $10.886
Saratoga Springs $11.568
Old Key West $12.044
Animal Kingdom $12.109
Aulani $12.207
Boulder Ridge $12.443
Vero Beach $14.755
Hilton Head $15.109

Interactive Chart Rendered Only in HTML

In [21]:
fig = plotly.graph_objects.Figure()
annotations = []
range_margin = (forecast_frame.ds.max() - forecast_frame.ds.min()) * 0.05
for resort in resorts:
    forecasted_years = numpy.array(forecast_frame['ds'][forecast_frame.resort == resort],dtype='datetime64[D]')
    forecasted_fees = numpy.array(forecast_frame['yhat'][forecast_frame.resort == resort])
    fig.add_trace(plotly.graph_objects.Scatter(x=forecasted_years, y=forecasted_fees, mode='lines',
        name=resort,
        line=dict(width=1.5),
        connectgaps=True,
    ))
    # labeling the right_side of the plot
    annotations.append(dict(xref='paper', x=0.96, y=forecasted_fees[-1],
                                  xanchor='left', yanchor='middle',
                                  text=resort + ' ${:,.2f}'.format(forecasted_fees[-1]),
                                  font=dict(
                                      family='Arial',
                                      size=7
                                  ),
                                  showarrow=False))
    
fig.update_layout(dict(
        title='Predicted Fees for Resorts',
        width=900,
        height=800,
        xaxis=dict(
            type='date',
            linecolor='rgb(204, 204, 204)', #alternatively make axis black
            linewidth=1.25,
            title='Year',
            range = [forecast_frame.ds.min() - range_margin, forecast_frame.ds.max() + range_margin],
            ticks='outside',
            tickfont=dict(
                family='Arial',
                size=12,
                color='rgb(82, 82, 82)',
            ),        
        ),
        yaxis=dict(
            title='Maintenance Fees',
            linecolor='rgb(204, 204, 204)', #alternatively make axis black
            linewidth=1.25,
            ticks='outside',
            tickfont=dict(
                family='Arial',
                size=12,
                color='rgb(82, 82, 82)',
            ),        
        ),
        autosize=False,
        margin=dict(
            autoexpand=False,
        ),
        showlegend=False,
        plot_bgcolor='white',
        colorway=plotly.colors.qualitative.Pastel,
    )
)
    
fig.update_layout(annotations=annotations)
fig.show()

Proposed Dues For 2021

In [22]:
maintenance_fees[['Resort','Year','Maintenance Fees']][maintenance_fees.Year == 2021]\
    .reset_index(drop=True)\
    .style.format({'Maintenance Fees':format_dollar}).hide_index()                                                                    
Out[22]:
Resort Year Maintenance Fees
Animal Kingdom 2021 $8.070
Aulani 2021 $8.350
Bay Lake Tower 2021 $6.900
Beach Club 2021 $7.440
Boardwalk 2021 $7.810
Boulder Ridge 2021 $8.110
Copper Creek 2021 $7.590
Grand Californian 2021 $6.990
Grand Floridian 2021 $6.810
Hilton Head 2021 $9.970
Old Key West 2021 $8.360
Polynesian 2021 $7.050
Riviera Resort 2021 $8.380
Saratoga Springs 2021 $7.110
Vero Beach 2021 $11.230

Cooper Creek has the lowest expected maintenance fees over the next 22 years

  1. Cooper Creek ($8.70)

  2. Grand Floridian ($10.96)

  3. Polynesian ($12.20)

Ignoring Riveria which just opened and has 3 data points